<!DOCTYPE html>

<html>
<head>
<title>Building Admin - Listing Customizations - QOR5 Document</title>

<meta name='description'>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<base href='/docs/'>

<link href='index.css' rel='stylesheet' type='text/css'>

<script type='text/javascript' defer src='index.js'></script>
</head>

<body>
<div id='app' v-cloak>
<div v-init-context:vars='{hideAside: false}' class='flex h-screen'>
<div class='flex-1 flex flex-col overflow-hidden'>
<div class='flex h-full'>
<aside v-show='!vars.hideAside' id='menuScroller' class='flex flex-col w-80 h-full bg-gray-50 border-r border-gray-200 overflow-y-auto'>
<div class='h-12'><search></search></div>

<ul class='px-0 py-3 mx-0 text-base font-normal list-none text-gray-700'>
<li class='m-0'>
<a href='index.html' id='index.html' onclick='window.storeMenuState("index.html")' class='inline-block px-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Introduction</a>
</li>

<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Getting Started</li>

<li class='m-0'>
<a href='getting-started/one-minute-quick-start.html' id='getting-started/one-minute-quick-start.html' onclick='window.storeMenuState("getting-started/one-minute-quick-start.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>1 Minute Quick Start</a>
</li>

<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Building Admin</li>

<li class='m-0'>
<a href='basics/presets-instant-crud.html' id='basics/presets-instant-crud.html' onclick='window.storeMenuState("basics/presets-instant-crud.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>presets, Instant CRUD</a>
</li>

<li class='m-0'>
<a href='basics/listing.html' id='basics/listing.html' onclick='window.storeMenuState("basics/listing.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Listing</a>
</li>

<li class='m-0'>
<a href='basics/listing-customizations.html' id='basics/listing-customizations.html' onclick='window.storeMenuState("basics/listing-customizations.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-blue-500'>Listing Customizations</a>
</li>

<li class='m-0'>
<a href='basics/filter.html' id='basics/filter.html' onclick='window.storeMenuState("basics/filter.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Filters</a>
</li>

<li class='m-0'>
<a href='presets-guide/editing-customizations.html' id='presets-guide/editing-customizations.html' onclick='window.storeMenuState("presets-guide/editing-customizations.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Editing</a>
</li>

<li class='m-0'>
<a href='basics/brand.html' id='basics/brand.html' onclick='window.storeMenuState("basics/brand.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Brand</a>
</li>

<li class='m-0'>
<a href='basics/menu.html' id='basics/menu.html' onclick='window.storeMenuState("basics/menu.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Menu</a>
</li>

<li class='m-0'>
<a href='presets-guide/detail-page-for-complex-object.html' id='presets-guide/detail-page-for-complex-object.html' onclick='window.storeMenuState("presets-guide/detail-page-for-complex-object.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Detailing</a>
</li>

<li class='m-0'>
<a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
</li>

<li class='m-0'>
<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
</li>

<li class='m-0'>
<a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
</li>

<li class='m-0'>
<a href='presets-guide/role.html' id='presets-guide/role.html' onclick='window.storeMenuState("presets-guide/role.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Role</a>
</li>

<li class='m-0'>
<a href='basics/shortcut.html' id='basics/shortcut.html' onclick='window.storeMenuState("basics/shortcut.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Keyboard Shortcut</a>
</li>

<li class='m-0'>
<a href='basics/confirm-dialog.html' id='basics/confirm-dialog.html' onclick='window.storeMenuState("basics/confirm-dialog.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Confirm Dialog</a>
</li>

<li class='m-0'>
<a href='slug.html' id='slug.html' onclick='window.storeMenuState("slug.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Slug</a>
</li>

<li class='m-0'>
<a href='seo.html' id='seo.html' onclick='window.storeMenuState("seo.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>SEO</a>
</li>

<li class='m-0'>
<a href='activity-log.html' id='activity-log.html' onclick='window.storeMenuState("activity-log.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Activity Log</a>
</li>

<li class='m-0'>
<a href='basics/worker.html' id='basics/worker.html' onclick='window.storeMenuState("basics/worker.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Worker</a>
</li>

<li class='m-0'>
<a href='basics/publish.html' id='basics/publish.html' onclick='window.storeMenuState("basics/publish.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Publish</a>
</li>

<li class='m-0'>
<a href='basics/i18n.html' id='basics/i18n.html' onclick='window.storeMenuState("basics/i18n.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Internationalization</a>
</li>

<li class='m-0'>
<a href='basics/l10n.html' id='basics/l10n.html' onclick='window.storeMenuState("basics/l10n.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Localization</a>
</li>

<li class='m-0'>
<a href='basics/redirection.html' id='basics/redirection.html' onclick='window.storeMenuState("basics/redirection.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Redirection</a>
</li>

<li class='m-0'>
<a href='basics/custom-page.html' id='basics/custom-page.html' onclick='window.storeMenuState("basics/custom-page.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Custom Pages</a>
</li>

<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Web Application</li>

<li class='m-0'>
<a href='advanced-functions/the-go-html-builder.html' id='advanced-functions/the-go-html-builder.html' onclick='window.storeMenuState("advanced-functions/the-go-html-builder.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>The Go HTML builder</a>
</li>

<li class='m-0'>
<a href='basics/page-func-and-event-func.html' id='basics/page-func-and-event-func.html' onclick='window.storeMenuState("basics/page-func-and-event-func.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Page Func and Event Func</a>
</li>

<li class='m-0'>
<a href='basics/layout-function-and-page-injector.html' id='basics/layout-function-and-page-injector.html' onclick='window.storeMenuState("basics/layout-function-and-page-injector.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout Function and Page Injector</a>
</li>

<li class='m-0'>
<a href='vuetify-components/lazy-portals.html' id='vuetify-components/lazy-portals.html' onclick='window.storeMenuState("vuetify-components/lazy-portals.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Lazy Portals</a>
</li>

<li class='m-0'>
<a href='basics/switch-pages-with-push-state.html' id='basics/switch-pages-with-push-state.html' onclick='window.storeMenuState("basics/switch-pages-with-push-state.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Switch Pages with Push State</a>
</li>

<li class='m-0'>
<a href='basics/reload-page-with-a-flash.html' id='basics/reload-page-with-a-flash.html' onclick='window.storeMenuState("basics/reload-page-with-a-flash.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Reload Page with a Flash</a>
</li>

<li class='m-0'>
<a href='basics/partial-refresh-with-portal.html' id='basics/partial-refresh-with-portal.html' onclick='window.storeMenuState("basics/partial-refresh-with-portal.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Partial Refresh with Portal</a>
</li>

<li class='m-0'>
<a href='basics/manipulate-page-url-in-event-func.html' id='basics/manipulate-page-url-in-event-func.html' onclick='window.storeMenuState("basics/manipulate-page-url-in-event-func.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Manipulate Page URL in Event Func</a>
</li>

<li class='m-0'>
<a href='basics/summary-of-event-response.html' id='basics/summary-of-event-response.html' onclick='window.storeMenuState("basics/summary-of-event-response.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Summary of Event Response</a>
</li>

<li class='m-0'>
<a href='basics/web-scope.html' id='basics/web-scope.html' onclick='window.storeMenuState("basics/web-scope.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>web.Scope</a>
</li>

<li class='m-0'>
<a href='basics/event-handling.html' id='basics/event-handling.html' onclick='window.storeMenuState("basics/event-handling.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Event Handling</a>
</li>

<li class='m-0'>
<a href='basics/form-handling.html' id='basics/form-handling.html' onclick='window.storeMenuState("basics/form-handling.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Form Handling</a>
</li>

<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>UI Components</li>

<li class='m-0'>
<a href='vuetify-components/basic-inputs.html' id='vuetify-components/basic-inputs.html' onclick='window.storeMenuState("vuetify-components/basic-inputs.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Basic Inputs</a>
</li>

<li class='m-0'>
<a href='vuetify-components/a-taste-of-using-vuetify-in-go.html' id='vuetify-components/a-taste-of-using-vuetify-in-go.html' onclick='window.storeMenuState("vuetify-components/a-taste-of-using-vuetify-in-go.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>A Taste of using Vuetify in Go</a>
</li>

<li class='m-0'>
<a href='vuetify-components/linkage-select.html' id='vuetify-components/linkage-select.html' onclick='window.storeMenuState("vuetify-components/linkage-select.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Linkage Select</a>
</li>

<li class='m-0'>
<a href='components-guide/composite-new-component-with-go.html' id='components-guide/composite-new-component-with-go.html' onclick='window.storeMenuState("components-guide/composite-new-component-with-go.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Composite new Component With Go</a>
</li>

<li class='m-0'>
<a href='components-guide/integrate-a-heavy-vue-component.html' id='components-guide/integrate-a-heavy-vue-component.html' onclick='window.storeMenuState("components-guide/integrate-a-heavy-vue-component.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Integrate a heavy Vue Component</a>
</li>

<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Appendix</li>

<li class='m-0'>
<a href='appendix/all-demo-examples.html' id='appendix/all-demo-examples.html' onclick='window.storeMenuState("appendix/all-demo-examples.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>All Demo Examples</a>
</li>
</ul>
</aside>

<main class='flex flex-col w-full bg-white overflow-x-hidden overflow-y-auto'>
<div id='docContentBox' class='flex flex-row w-full'>
<div class='flex flex-grow flex-col w-2/3'>
<div class='flex flex-row'>
<button @click='vars.hideAside = !vars.hideAside' class='w-12 h-12 p-4'>
<div class='w-4 h-4 fill-current text-gray-300'>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 2 12 L 2 11 L 14 11 L 14 12 Z M 2 8.5 L 2 7.5 L 14 7.5 L 14 8.5 Z M 2 5 L 2 4 L 14 4 L 14 5 Z M 2 5 "/>
</g>
</svg>
</div>
</button>
</div>

<div id='docMainBox' class='px-16 pb-12 pt-4 overflow-auto'>
<h1 class='mb-8'>Listing Customizations</h1>

<div class='border-t'><p>We get a default listing page with default columns, But default columns from database
columns rarely fit the needs for any real application.</p>

<h2 id='change-list-columns-and-component-of-field'>Change List Columns and Component of Field
<a href='#change-list-columns-and-component-of-field' class='anchor'></a>
</h2>
<p>Here is how do we change the columns of the list and how to we change the content display of a columns.</p>

<highlightjs :language='"go"' :code='"\nfunc PresetsRowMenuAction(b *presets.Builder, db *gorm.DB) (\n\tmb *presets.ModelBuilder,\n\tcl *presets.ListingBuilder,\n\tce *presets.EditingBuilder,\n\tdp *presets.DetailingBuilder,\n) {\n\tmb, cl, ce, dp = PresetsHelloWorld(b, db)\n\tcl.KeywordSearchOff(true)\n\trmb := cl.RowMenu()\n\n\trmb.RowMenuItem(\"with-icon\").Icon(\"mdi-close\")\n\trmb.RowMenuItem(\"Delete\").ComponentFunc(func(obj interface{}, id string, ctx *web.EventContext) h.HTMLComponent {\n\t\treturn nil\n\t})\n\treturn\n}\n\ntype Company struct {\n\tID   int\n\tName string\n}\n\nfunc PresetsListingCustomizationFields(b *presets.Builder, db *gorm.DB) (\n\tmb *presets.ModelBuilder,\n\tcl *presets.ListingBuilder,\n\tce *presets.EditingBuilder,\n\tdp *presets.DetailingBuilder,\n) {\n\tb.GetI18n().\n\t\tSupportLanguages(language.English, language.SimplifiedChinese).\n\t\tRegisterForModule(language.SimplifiedChinese, presets.ModelsI18nModuleKey, Messages_zh_CN).\n\t\tRegisterForModule(language.English, presets.ModelsI18nModuleKey, Messages_en_US)\n\n\tmb, cl, ce, dp = PresetsHelloWorld(b, db)\n\n\tcl = mb.Listing(\"ID\", \"Name\", \"Company\", \"Email\").\n\t\tSearchColumns(\"name\", \"email\").SelectableColumns(true).\n\t\tOrderableFields([]*presets.OrderableField{\n\t\t\t{\n\t\t\t\tFieldName: \"ID\",\n\t\t\t\tDBColumn:  \"id\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tFieldName: \"Name\",\n\t\t\t\tDBColumn:  \"name\",\n\t\t\t},\n\t\t})\n\n\t// if you want to customize the table head, you can use WrapColumns\n\tcl.WrapColumns(presets.CustomizeColumnHeader(func(evCtx *web.EventContext, col *presets.Column, th h.MutableAttrHTMLComponent) (h.MutableAttrHTMLComponent, error) {\n\t\tth.SetAttr(\"style\", \"min-width: 123px; color: red;\")\n\t\treturn th, nil\n\t}, \"ID\"))\n\n\tcl.Field(\"Company\").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {\n\t\tc := obj.(*Customer)\n\t\tvar comp Company\n\t\tif c.CompanyID == 0 {\n\t\t\treturn h.Td()\n\t\t}\n\n\t\tdb.First(\u0026comp, \"id = ?\", c.CompanyID)\n\t\treturn h.Td(\n\t\t\th.A().Text(comp.Name).\n\t\t\t\tClass(\"text-decoration-none\", \"text-blue\").\n\t\t\t\tHref(\"javascript:void(0)\").\n\t\t\t\tAttr(\"@click.stop\",\n\t\t\t\t\tweb.POST().EventFunc(actions.Edit).\n\t\t\t\t\t\tQuery(presets.ParamID, fmt.Sprint(comp.ID)).\n\t\t\t\t\t\tURL(\"companies\").\n\t\t\t\t\t\tGo()),\n\t\t\th.Text(\"-\"),\n\t\t\th.A().Text(\"(Open in Dialog)\").\n\t\t\t\tClass(\"text-decoration-none\", \"text-blue\").\n\t\t\t\tHref(\"javascript:void(0)\").\n\t\t\t\tAttr(\"@click.stop\",\n\t\t\t\t\tweb.POST().EventFunc(actions.Edit).\n\t\t\t\t\t\tQuery(presets.ParamID, fmt.Sprint(comp.ID)).\n\t\t\t\t\t\tQuery(presets.ParamOverlay, actions.Dialog).\n\t\t\t\t\t\tURL(\"companies\").\n\t\t\t\t\t\tGo(),\n\t\t\t\t),\n\t\t)\n\t})\n\n\tcl.Field(\"Name\").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {\n\t\tc := obj.(*Customer)\n\t\treturn h.Td(\n\t\t\th.Div(h.Text(c.Name + \"_\" + \"customizable\")),\n\t\t)\n\t})\n\n\tce = mb.Editing(\"Name\", \"CompanyID\")\n\n\tmb.RegisterEventFunc(\"updateCompanyList\", func(ctx *web.EventContext) (r web.EventResponse, err error) {\n\t\tcompanyID := ctx.ParamAsInt(presets.ParamOverlayUpdateID)\n\t\tr.UpdatePortals = append(r.UpdatePortals, \u0026web.PortalUpdate{\n\t\t\tName: \"companyListPortal\",\n\t\t\tBody: companyList(ctx, db, companyID),\n\t\t})\n\t\treturn\n\t})\n\n\tce.Field(\"CompanyID\").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {\n\t\tc := obj.(*Customer)\n\t\treturn web.Portal(companyList(ctx, db, c.CompanyID)).Name(\"companyListPortal\")\n\t})\n\n\tcomp := b.Model(\u0026Company{})\n\tcomp.Editing().ValidateFunc(func(obj interface{}, ctx *web.EventContext) (err web.ValidationErrors) {\n\t\tc := obj.(*Company)\n\t\tif len(c.Name) \u003c 5 {\n\t\t\terr.GlobalError(\"name must longer than 5\")\n\t\t}\n\t\treturn\n\t})\n\n\tgcm, err := cursor.NewGCM([]byte(\"0123456789abcdef0123456789abcdef\"))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tgcmMiddleware := cursor.GCM[any](gcm)\n\tcl.RelayPagination(\n\t\tgorm2op.KeysetBasedPagination(true, gcmMiddleware),\n\t)\n\tcomp.Listing().RelayPagination(\n\t\tgorm2op.KeysetBasedPagination(true, gcmMiddleware),\n\t)\n\n\treturn\n}\n\nfunc companyList(ctx *web.EventContext, db *gorm.DB, companyID int) h.HTMLComponent {\n\tmsgr := i18n.MustGetModuleMessages(ctx.R, presets.ModelsI18nModuleKey, Messages_en_US).(*Messages)\n\tvar comps []Company\n\tdb.Find(\u0026comps)\n\treturn h.Div(\n\t\tv.VSelect().\n\t\t\tLabel(msgr.CustomersCompanyID).\n\t\t\tVariant(\"underlined\").\n\t\t\tItems(comps).\n\t\t\tAttr(web.VField(\"CompanyID\", companyID)...).\n\t\t\tItemTitle(\"Name\").ItemValue(\"ID\"),\n\t\th.A().Text(\"Add Company\").\n\t\t\tClass(\"text-decoration-none\", \"text-blue\").\n\t\t\tHref(\"javascript:void(0)\").Attr(\"@click\",\n\t\t\tweb.POST().\n\t\t\t\tURL(\"companies\").\n\t\t\t\tEventFunc(actions.New).\n\t\t\t\tQuery(presets.ParamOverlay, actions.Dialog).\n\t\t\t\tQuery(presets.ParamOverlayAfterUpdateScript,\n\t\t\t\t\tweb.POST().EventFunc(\"updateCompanyList\").Go()).\n\t\t\t\tGo(),\n\t\t),\n\t)\n}\n"'></highlightjs>

<div>
<div class='demo'>
<a href='/examples/presets-listing-customization-fields/customers' target='_blank'>Check the demo</a>
 | 
<a href='https://github.com/qor5/admin/tree/main/docs/docsrc/examples/examples_presets/listing.go#L83-L242' target='_blank'>Source on GitHub</a>
</div>
</div>
<p>What we did with above code:</p>

<ul>
<li>Added a new field to listing table that not exists on the struct <code>Customer</code></li>
<li>Define the listing display for the listing table by using the <code>Td()</code> and fetch the company data from a different table with associated column value</li>
<li>Link the company name in the listing to link the edit drawer of company</li>
<li>Limit the edit drawer field to only have <code>Name</code> and <code>CompanyID</code></li>
<li>Made the <code>CompanyID</code> field a vuetify <code>VSelect</code> component</li>
<li>Add companies as a new navigation item, that you can manage companies data</li>
<li><code>.SearchColumns(&#34;name&#34;, &#34;email&#34;)</code> configure the top navigation search box searches which columns with sql like operation</li>
</ul>

<h2 id='filters-panel'>Filters Panel
<a href='#filters-panel' class='anchor'></a>
</h2>
<p>Here we continue to add filters for the list</p>

<highlightjs :language='"go"' :code='"\nfunc PresetsListingCustomizationFilters(b *presets.Builder, db *gorm.DB) (\n\tmb *presets.ModelBuilder,\n\tcl *presets.ListingBuilder,\n\tce *presets.EditingBuilder,\n\tdp *presets.DetailingBuilder,\n) {\n\tmb, cl, ce, dp = PresetsListingCustomizationFields(b, db)\n\n\tcl.FilterDataFunc(func(ctx *web.EventContext) vuetifyx.FilterData {\n\t\tmsgr := i18n.MustGetModuleMessages(ctx.R, presets.ModelsI18nModuleKey, Messages_en_US).(*Messages)\n\t\tvar companyOptions []*vuetifyx.SelectItem\n\t\terr := db.Model(\u0026Company{}).Select(\"name as text, id as value\").Scan(\u0026companyOptions).Error\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\treturn []*vuetifyx.FilterItem{\n\t\t\t{\n\t\t\t\tKey:      \"created\",\n\t\t\t\tLabel:    msgr.CustomersFilterCreated,\n\t\t\t\tItemType: vuetifyx.ItemTypeDatetimeRange,\n\t\t\t\t// SQLCondition: `cast(strftime(&#39;%%s&#39;, created_at) as INTEGER) %s ?`,\n\t\t\t\tSQLCondition: `created_at %s ?`,\n\t\t\t\tDateOptions:  \u0026[]vuetifyx.DateOption{{Label: \"StartAt\"}, {Label: \"EndAt\"}},\n\n\t\t\t\tValidateFunc: func(ctx *web.EventContext, vErr *web.ValidationErrors, it *vuetifyx.FilterItem) {\n\t\t\t\t\tif it.ValueFrom \u003e= it.ValueTo {\n\t\t\t\t\t\tvErr.GlobalError(\"CreatedAt Error\")\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tKey:      \"approved\",\n\t\t\t\tLabel:    msgr.CustomersFilterApproved,\n\t\t\t\tItemType: vuetifyx.ItemTypeDatetimeRange,\n\t\t\t\t// SQLCondition: `cast(strftime(&#39;%%s&#39;, created_at) as INTEGER) %s ?`,\n\t\t\t\tSQLCondition: `created_at %s ?`,\n\t\t\t\tDateOptions:  \u0026[]vuetifyx.DateOption{{Label: \"Approved_Start_At\", ClearText: \"Cancel1\"}, {Label: \"Approved_End_At\"}},\n\t\t\t\tValidateFunc: func(ctx *web.EventContext, vErr *web.ValidationErrors, it *vuetifyx.FilterItem) {\n\t\t\t\t\tif it.ValueFrom \u003e= it.ValueTo {\n\t\t\t\t\t\tvErr.GlobalError(\"ApprovedAt Error\")\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tKey:          \"name\",\n\t\t\t\tLabel:        msgr.CustomersFilterName,\n\t\t\t\tItemType:     vuetifyx.ItemTypeString,\n\t\t\t\tSQLCondition: `name %s ?`,\n\t\t\t},\n\t\t\t{\n\t\t\t\tKey:          \"company\",\n\t\t\t\tLabel:        msgr.CustomersFilterCompany,\n\t\t\t\tItemType:     vuetifyx.ItemTypeSelect,\n\t\t\t\tSQLCondition: `company_id %s ?`,\n\t\t\t\tOptions:      companyOptions,\n\t\t\t},\n\t\t}\n\t})\n\treturn\n}\n"'></highlightjs>

<div>
<div class='demo'>
<a href='/examples/presets-listing-customization-filters/customers' target='_blank'>Check the demo</a>
 | 
<a href='https://github.com/qor5/admin/tree/main/docs/docsrc/examples/examples_presets/listing.go#L244-L308' target='_blank'>Source on GitHub</a>
</div>
</div>
<p><code>FilterDataFunc</code> of <code>presets.ListingBuilder</code> setup to have the filter menu or not.
And how it will combine the sql conditions when doing query. the filter menu will
change the url query strings with the filter values.</p>

<p>Current we support these types</p>

<ul>
<li><code>ItemTypeDate</code>: set it as a date filter item, which have many switches to support date and date range</li>
<li><code>ItemTypeNumber</code>: set it to a number filter item, which have switches to support number and number range</li>
<li><code>ItemTypeString</code>: set it to a string filter item, which have contains, and match exactly</li>
<li><code>ItemTypeSelect</code>: set it to a select filter item, which have a options of values for selection</li>
</ul>

<h2 id='filter-tabs'>Filter Tabs
<a href='#filter-tabs' class='anchor'></a>
</h2>
<p>Filter tabs is based on Filters configuration. But display as tabs above the list,
You can think it as a short cut that used very frequently to filter something instead of
use the pop up panel of filter.</p>

<highlightjs :language='"go"' :code='"\nfunc PresetsListingCustomizationTabs(b *presets.Builder, db *gorm.DB) (\n\tmb *presets.ModelBuilder,\n\tcl *presets.ListingBuilder,\n\tce *presets.EditingBuilder,\n\tdp *presets.DetailingBuilder,\n) {\n\tmb, cl, ce, dp = PresetsListingCustomizationFilters(b, db)\n\n\tcl.FilterTabsFunc(func(ctx *web.EventContext) []*presets.FilterTab {\n\t\tvar c Company\n\t\tdb.First(\u0026c)\n\t\treturn []*presets.FilterTab{\n\t\t\t{\n\t\t\t\tLabel: \"All\",\n\t\t\t\tQuery: url.Values{},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLabel: \"Felix\",\n\t\t\t\tQuery: url.Values{\"name.ilike\": []string{\"felix\"}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLabel: \"The Plant\",\n\t\t\t\tQuery: url.Values{\"company\": []string{fmt.Sprint(c.ID)}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLabel: \"Approved\",\n\t\t\t\tQuery: url.Values{\"approved.gte\": []string{time.Time{}.Format(\"2006-01-02 15:04\")}},\n\t\t\t},\n\t\t}\n\t})\n\treturn\n}\n"'></highlightjs>

<div>
<div class='demo'>
<a href='/examples/presets-listing-customization-tabs/customers' target='_blank'>Check the demo</a>
 | 
<a href='https://github.com/qor5/admin/tree/main/docs/docsrc/examples/examples_presets/listing.go#L310-L345' target='_blank'>Source on GitHub</a>
</div>
</div>
<p><code>Query</code> string name must be from the Filter&#39;s item configuration key field.</p>

<h2 id='bulk-actions'>Bulk Actions
<a href='#bulk-actions' class='anchor'></a>
</h2>
<p>Bulk actions makes the list row show checkboxes, and you can select one or many rows,
Later do an bulk update data for all of them.</p>

<p>Here is how to use it:</p>

<highlightjs :language='"go"' :code='"\nfunc PresetsListingCustomizationBulkActions(b *presets.Builder, db *gorm.DB) (\n\tmb *presets.ModelBuilder,\n\tcl *presets.ListingBuilder,\n\tce *presets.EditingBuilder,\n\tdp *presets.DetailingBuilder,\n) {\n\tmb, cl, ce, _ = PresetsListingCustomizationTabs(b, db)\n\n\tcl.BulkAction(\"Approve\").Label(\"Approve\").\n\t\tUpdateFunc(func(selectedIds []string, ctx *web.EventContext, r *web.EventResponse) (err error) {\n\t\t\tcomment := ctx.R.FormValue(\"ApprovalComment\")\n\t\t\tif len(comment) \u003c 10 {\n\t\t\t\tctx.Flash = \"comment should larger than 10\"\n\t\t\t\treturn\n\t\t\t}\n\t\t\terr = db.Model(\u0026Customer{}).\n\t\t\t\tWhere(\"id IN (?)\", selectedIds).\n\t\t\t\tUpdates(map[string]interface{}{\"approved_at\": time.Now(), \"approval_comment\": comment}).Error\n\t\t\tif err != nil {\n\t\t\t\tctx.Flash = err.Error()\n\t\t\t} else {\n\t\t\t\tr.Emit(\n\t\t\t\t\tpresets.NotifModelsUpdated(\u0026Customer{}),\n\t\t\t\t\tpresets.PayloadModelsUpdated{Ids: selectedIds},\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn\n\t\t}).\n\t\tComponentFunc(func(selectedIds []string, ctx *web.EventContext) h.HTMLComponent {\n\t\t\tcomment := ctx.R.FormValue(\"ApprovalComment\")\n\t\t\terrorMessage := \"\"\n\t\t\tif ctx.Flash != nil {\n\t\t\t\terrorMessage = ctx.Flash.(string)\n\t\t\t}\n\t\t\treturn v.VTextField().\n\t\t\t\tVariant(\"underlined\").\n\t\t\t\tAttr(web.VField(\"ApprovalComment\", comment)...).\n\t\t\t\tLabel(\"Content\").\n\t\t\t\tErrorMessages(errorMessage)\n\t\t})\n\n\tcl.BulkAction(\"Delete\").Label(\"Delete\").\n\t\tUpdateFunc(func(selectedIds []string, ctx *web.EventContext, r *web.EventResponse) (err error) {\n\t\t\terr = db.Where(\"id IN (?)\", selectedIds).Delete(\u0026Customer{}).Error\n\t\t\tif err == nil {\n\t\t\t\tr.Emit(\n\t\t\t\t\tpresets.NotifModelsDeleted(\u0026Customer{}),\n\t\t\t\t\tpresets.PayloadModelsDeleted{\n\t\t\t\t\t\tIds: selectedIds,\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn\n\t\t}).\n\t\tComponentFunc(func(selectedIds []string, ctx *web.EventContext) h.HTMLComponent {\n\t\t\treturn h.Div().Text(fmt.Sprintf(\"Are you sure you want to delete %s ?\", selectedIds)).Class(\"title deep-orange--text\")\n\t\t})\n\n\treturn\n}\n"'></highlightjs>

<div>
<div class='demo'>
<a href='/examples/presets-listing-customization-bulk-actions/customers' target='_blank'>Check the demo</a>
 | 
<a href='https://github.com/qor5/admin/tree/main/docs/docsrc/examples/examples_presets/listing.go#L347-L410' target='_blank'>Source on GitHub</a>
</div>
</div>
<ul>
<li><code>ComponentFunc</code> of the bulk action configure the component that will show to user to input after user clicked the bulk action button</li>
<li><code>UpdateFunc</code> configure the logic that the bulk action execute</li>
</ul>

<h2 id='search-func'>Search Func
<a href='#search-func' class='anchor'></a>
</h2>
<p><code>SearchFunc</code> defines a data processing function for <code>ListingBuilder</code>.
This function searches for a model based on the specified search parameters.
It returns the search results along with the total count of matching records.
You can process the data displayed on the listing page here based on context or custom conditions before pagination.</p>

<p>In the following example, the listing page only displays approved customers.</p>

<highlightjs :language='"go"' :code='"\nfunc PresetsListingCustomizationSearcher(b *presets.Builder, db *gorm.DB) (\n\tmb *presets.ModelBuilder,\n\tcl *presets.ListingBuilder,\n\tce *presets.EditingBuilder,\n\tdp *presets.DetailingBuilder,\n) {\n\tb.DataOperator(gorm2op.DataOperator(db))\n\tmb = b.Model(\u0026Customer{})\n\tmb.Listing().SearchFunc(func(ctx *web.EventContext, params *presets.SearchParams) (result *presets.SearchResult, err error) {\n\t\t// only display approved customers\n\t\tqdb := db.Where(\"approved_at IS NOT NULL\")\n\t\treturn gorm2op.DataOperator(qdb).Search(ctx, params)\n\t})\n\treturn\n}\n"'></highlightjs>

<div>
<div class='demo'>
<a href='/examples/presets-listing-customization-searcher/customers' target='_blank'>Check the demo</a>
 | 
<a href='https://github.com/qor5/admin/tree/main/docs/docsrc/examples/examples_presets/listing.go#L412-L430' target='_blank'>Source on GitHub</a>
</div>
</div>
</div>
</div>
</div>

<div class='font-medium text-base hidden xl:block text-gray-600 pt-4'>
<div class='sticky top-4 w-52'>On This Page<toc></toc></div>
</div>
</div>
<search-result></search-result></main>
</div>
</div>
</div>
</div>
</body>
</html>
